/**********************************************************************
 * Copyright (c) 2014 HubSpot Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **********************************************************************/
package com.hubspot.jinjava.lib.tag;

import com.google.common.collect.ImmutableMap;
import com.hubspot.algebra.Result;
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
import com.hubspot.jinjava.doc.annotations.JinjavaParam;
import com.hubspot.jinjava.doc.annotations.JinjavaSnippet;
import com.hubspot.jinjava.doc.annotations.JinjavaTextMateSnippet;
import com.hubspot.jinjava.interpret.AutoCloseableSupplier.AutoCloseableImpl;
import com.hubspot.jinjava.interpret.InterpretException;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.interpret.TagCycleException;
import com.hubspot.jinjava.interpret.TemplateError;
import com.hubspot.jinjava.interpret.TemplateError.ErrorItem;
import com.hubspot.jinjava.interpret.TemplateError.ErrorReason;
import com.hubspot.jinjava.interpret.TemplateError.ErrorType;
import com.hubspot.jinjava.interpret.TemplateSyntaxException;
import com.hubspot.jinjava.interpret.errorcategory.BasicTemplateErrorCategory;
import com.hubspot.jinjava.tree.Node;
import com.hubspot.jinjava.tree.TagNode;
import com.hubspot.jinjava.util.HelperStringTokenizer;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;

@JinjavaDoc(
  value = "includes multiple files in one template or stylesheet",
  params = {
    @JinjavaParam(
      value = "path",
      desc = "Design Manager path to the file that you would like to include"
    ),
  },
  snippets = {
    @JinjavaSnippet(code = "{% include \"custom/page/web_page_basic/my_footer.html\" %}"),
    @JinjavaSnippet(code = "{% include \"generated_global_groups/2781996615.html\" %}"),
    @JinjavaSnippet(code = "{% include \"hubspot/styles/patches/recommended.css\" %}"),
  }
)
@JinjavaTextMateSnippet(code = "{% include '${1:path}' %}")
public class IncludeTag implements Tag {

  public static final String TAG_NAME = "include";

  private static final long serialVersionUID = -8391753639874726854L;

  @Override
  public String interpret(TagNode tagNode, JinjavaInterpreter interpreter) {
    HelperStringTokenizer helper = new HelperStringTokenizer(tagNode.getHelpers());
    if (!helper.hasNext()) {
      throw new TemplateSyntaxException(
        tagNode.getMaster().getImage(),
        "Tag 'include' expects template path",
        tagNode.getLineNumber(),
        tagNode.getStartPosition()
      );
    }

    String path = StringUtils.trimToEmpty(helper.next());
    String templateFile = interpreter.resolveString(
      path,
      tagNode.getLineNumber(),
      tagNode.getStartPosition()
    );
    templateFile = interpreter.resolveResourceLocation(templateFile);

    final String finalTemplateFile = templateFile;
    final TagNode finalTagNode = tagNode;
    try (
      AutoCloseableImpl<Result<String, TagCycleException>> includeStackWrapper =
        interpreter
          .getContext()
          .getIncludePathStack()
          .closeablePush(
            finalTemplateFile,
            tagNode.getLineNumber(),
            tagNode.getStartPosition()
          )
          .get();
      AutoCloseableImpl<Result<String, TagCycleException>> currentPathWrapper =
        interpreter
          .getContext()
          .getCurrentPathStack()
          .closeablePush(
            finalTemplateFile,
            interpreter.getLineNumber(),
            interpreter.getPosition()
          )
          .get()
    ) {
      return includeStackWrapper
        .value()
        .match(
          err -> {
            interpreter.addError(
              new TemplateError(
                ErrorType.WARNING,
                ErrorReason.EXCEPTION,
                ErrorItem.TAG,
                "Include cycle detected for path: '" + finalTemplateFile + "'",
                null,
                finalTagNode.getLineNumber(),
                finalTagNode.getStartPosition(),
                err,
                BasicTemplateErrorCategory.INCLUDE_CYCLE_DETECTED,
                ImmutableMap.of("path", finalTemplateFile)
              )
            );
            return "";
          },
          includeStackPath ->
            currentPathWrapper
              .value()
              .match(
                err -> {
                  interpreter.addError(
                    new TemplateError(
                      ErrorType.WARNING,
                      ErrorReason.EXCEPTION,
                      ErrorItem.TAG,
                      "Include cycle detected for path: '" + finalTemplateFile + "'",
                      null,
                      finalTagNode.getLineNumber(),
                      finalTagNode.getStartPosition(),
                      err,
                      BasicTemplateErrorCategory.INCLUDE_CYCLE_DETECTED,
                      ImmutableMap.of("path", finalTemplateFile)
                    )
                  );
                  return "";
                },
                currentPath -> {
                  try {
                    String template = interpreter.getResource(finalTemplateFile);
                    Node node = interpreter.parse(template);
                    interpreter
                      .getContext()
                      .addDependency("coded_files", finalTemplateFile);
                    return interpreter.render(node, false);
                  } catch (IOException e) {
                    throw new InterpretException(
                      e.getMessage(),
                      e,
                      finalTagNode.getLineNumber(),
                      finalTagNode.getStartPosition()
                    );
                  }
                }
              )
        );
    }
  }

  @Override
  public String getEndTagName() {
    return null;
  }

  @Override
  public String getName() {
    return TAG_NAME;
  }
}
